#pragma once
#include <limits>
#include <cstdint>
#include <typeindex>
#include <type_traits>
#include <unordered_map>

#include "./Scene3D.hpp"
#include "./Camera3D.hpp"
#include "./Object3D.hpp"

struct NonCopyable {
    NonCopyable() {}
    NonCopyable(const NonCopyable &) = delete;
    NonCopyable &operator=(const NonCopyable &) = delete;
};

class Renderer : NonCopyable {
public:
    Renderer();
    Renderer(const Renderer &) = delete;
    Renderer(Renderer &&) = delete;
    virtual ~Renderer() = 0;
    Renderer &operator=(const Renderer &) = delete;
    Renderer &operator=(Renderer &&) = delete;

public:
    void render(Scene3D *scene, Camera3D *camera);

protected:
    using Handle = std::size_t;
    // Manage GPU resources
    class RenderData : NonCopyable {
    public:
        RenderData() {}
        virtual ~RenderData() = 0;

    public:
        Handle vertexBuffer{0};
        Handle indexBuffer{0};
        Handle constantBuffer{0};
        Handle renderPipeline{0};
    };
    class RenderObject : NonCopyable {
    public:
        RenderObject() : m_object(nullptr), m_renderData(), m_typeHandle(0xFFFF) {}

    public:
        Object3D *m_object;
        std::unique_ptr<RenderData> m_renderData;
        Handle m_typeHandle;
    };

protected:
    template <typename T> Handle registerType() {
        static_assert(std::is_base_of<Object3D, T>::value, "T should be derived from Object3D");
        std::type_index index(typeid(T));
        if (m_typeHandles.find(index) == m_typeHandles.end()) {
            Handle handle = m_typeHandles.size();
            m_typeHandles[index] = handle;
            m_initRenderDataFuncs.emplace_back(nullptr);
            m_updateRenderDataFuncs.emplace_back(nullptr);
            m_renderFuncs.emplace_back(nullptr);
            return handle;
        } else {
            return m_typeHandles[index];
        }
    }
    void setinitRenderDataFunc(Handle typeHandle, void (Renderer::*func)(RenderObject *)) {
        m_initRenderDataFuncs.at(typeHandle) = func;
    }
    void setUpdateRenderDataFunc(Handle typeHandle, void (Renderer::*func)(RenderObject *)) {
        m_updateRenderDataFuncs.at(typeHandle) = func;
    }
    void setRenderFunc(Handle typeHandle, void (Renderer::*func)(RenderObject *)) {
        m_renderFuncs.at(typeHandle) = func;
    }

protected:
    void initRenderData(RenderObject *object) { (this->*m_initRenderDataFuncs[object->m_typeHandle])(object); }
    void updateRenderData(RenderObject *object) {
        if (object->m_object->m_needUpdateRenderData) {
            (this->*m_updateRenderDataFuncs[object->m_typeHandle])(object);
            object->m_object->m_needUpdateRenderData = false;
        }
    }
    void render(RenderObject *object) { (this->*m_renderFuncs[object->m_typeHandle])(object); }

protected:
    // Types
    std::unordered_map<std::type_index, Handle> m_typeHandles{};
    std::vector<void (Renderer::*)(RenderObject *)> m_initRenderDataFuncs{};
    std::vector<void (Renderer::*)(RenderObject *)> m_updateRenderDataFuncs{};
    std::vector<void (Renderer::*)(RenderObject *)> m_renderFuncs{};

protected:
    // Instances
    // std::vector<RenderObject> ddd;
};